package reliability;

import reliability.model.GraphModel;
import reliability.model.MinimalPathsAndSectionsMethodResults;
import reliability.types.graph.Path;
import reliability.types.graph.Paths;
import reliability.types.graph.PathsInfo;
import reliability.types.graph.VectorOfMinimalPaths;

import javax.swing.*;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

public class MinimalPathsAndSectionsMethod extends SwingWorker<MinimalPathsAndSectionsMethodResults, Void>
{
    private GraphModel graph;

    private class Section extends TreeSet<Integer> {}

    private class SectionsComparator
        implements Comparator<Section>
    {
        public int compare(Section section1, Section section2)
        {
            int result = section1.size() - section2.size();
            if (result == 0)
            {
                result = (section1.equals(section2)) ? 0 : 1;
            }
            return result;
        }
    }

    private class SetOfSections
        extends ConcurrentSkipListSet<Section>
    {
        public SetOfSections()
        {
            super(new SectionsComparator());
        }
    }

    public MinimalPathsAndSectionsMethod(GraphModel graph)
    {
        this.graph = graph;
    }

    public MinimalPathsAndSectionsMethodResults doInBackground()
    {
        setProgress(0);
        return processNetwork();
    }

    private MinimalPathsAndSectionsMethodResults processNetwork()
    {
        VectorOfMinimalPaths vectorOfMinimalPaths = graph.getMinimalPaths();
        double minimalReliability = getMinimalReliability(vectorOfMinimalPaths);
        double maximumReliability = getMaximalReliability(vectorOfMinimalPaths);
        setProgress(100);
        return new MinimalPathsAndSectionsMethodResults(minimalReliability, maximumReliability);
    }

    private double getMinimalReliability(VectorOfMinimalPaths vectorOfMinimalPaths)
    {
        double minimalReliability = 1.0;
        int vectorSize = vectorOfMinimalPaths.size();
        int notificationStep = Math.min(vectorSize / 100, 1);
        int previousNotificationValue = 0;
        for (int i = 0; i < vectorSize; i++)
        {
            if (i >= previousNotificationValue + notificationStep)
            {
                previousNotificationValue = i;
                float progressPercentage = ((float) i / (float) vectorSize) * 100;
                setProgress(Math.round(progressPercentage));
            }
            PathsInfo minimalPathsInfo = vectorOfMinimalPaths.get(i);
            SetOfSections minimalSections = getMinimalSections(minimalPathsInfo.paths);
            double sectionsReliability = computeSectionsReliability(minimalSections);
            minimalReliability = (sectionsReliability < minimalReliability) ? sectionsReliability : minimalReliability;
        }
        return minimalReliability;
    }

    private double getMaximalReliability(VectorOfMinimalPaths vectorOfMinimalPaths)
    {
        double maximalReliability = 0.0;
        for (PathsInfo minimalPathsInfo : vectorOfMinimalPaths)
        {
            double pathsReliability = computePathsReliability(minimalPathsInfo.paths);
            maximalReliability = (pathsReliability > maximalReliability) ? pathsReliability : maximalReliability;
        }
        return maximalReliability;
    }

    private double computePathsReliability(Paths minimalPaths)
    {
        double product = 1;
        for (Path minimalPath : minimalPaths)
        {
            double pathReliability = 1;
            for (Integer key: minimalPath)
            {
                pathReliability *= graph.getElement(key).getReliability();
            }
            product *= 1 - pathReliability;
        }
        return 1 - product;
    }

    private double computeSectionsReliability(SetOfSections setOfSections)
    {
        double reliability = 1;
        for (Section section : setOfSections)
        {
            double product = 1;
            for (Integer key : section)
            {
                product *= 1 - graph.getElement(key).getReliability();
            }
            reliability *= 1 - product;
        }
        return reliability;
    }

    private SetOfSections getMinimalSections(Paths paths)
    {
        SetOfSections setOfSections = new SetOfSections();
        setOfSections.add(new Section());
        for (Path path : paths)
        {
            setOfSections = combineSections(setOfSections, path);
        }
        return setOfSections;
    }

    private SetOfSections combineSections(SetOfSections setOfSections, Path path)
    {
        SetOfSections combinedSections = new SetOfSections();
        for (Section section : setOfSections)
        {
            for (Integer key : path)
            {
                Section curSection = (Section) section.clone();
                curSection.add(key);
                combinedSections.add(curSection);
            }
        }
        return getOptimizedSetOfSections(combinedSections);
    }

    private SetOfSections getOptimizedSetOfSections(SetOfSections setOfSections)
    {
        SetOfSections optimizedSetOfSections = new SetOfSections();
        Section [] sections = setOfSections.toArray(new Section [setOfSections.size()]);
        for (int i = 0; i < sections.length; i++)
        {
            boolean hasSubsets = false;
            for (int j = 0; j < i; j++)
            {
                if (sections[i].containsAll(sections[j]))
                {
                    hasSubsets = true;
                    break;
                }
            }
            if (!hasSubsets)
            {
                optimizedSetOfSections.add(sections[i]);
            }
        }
        return optimizedSetOfSections;
    }

}
